package com.lhy.admin.common.mvcplus.wrapper;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lhy.admin.common.mvcplus.entity.ComparatorEnum;
import com.lhy.admin.common.mvcplus.entity.ConditionEnum;
import com.lhy.admin.common.mvcplus.entity.DbField;
import com.lhy.admin.common.mvcplus.entity.SearchObject;
import com.lhy.admin.common.mybatis.handlers.Java8TimeJacksonTypeHandler;
import com.lhy.admin.util.ReflectionUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.TypeHandler;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;

/**
 * WrapperHelper mybatis-plus wrapper辅助类 用于创建具体wrapper
 *
 * @author hyluan
 * @date 2022/6/24 17:41
 */
@Slf4j
public class WrapperHelper {

    private static final Map<ComparatorEnum, SearchComparator> SEARCH_COMPARATOR_MAP = new HashMap<>();

    static {
        ServiceLoader<SearchComparator> serviceLoader = ServiceLoader.load(SearchComparator.class);
        for (SearchComparator whereCondition : serviceLoader) {
            SEARCH_COMPARATOR_MAP.put(whereCondition.comparatorEnum(), whereCondition);
        }
    }

    /**
     * isCanLikeColumn
     * 对象是否可以进行like比较
     *
     * @param v v
     * @return {@link boolean}
     * @author hyluan
     * @date 2022/6/24 17:42
     */
    private static boolean isCanLikeColumn(Object v) {
        if (v == null) {
            return false;
        }
        return String.class.isAssignableFrom(v.getClass());
    }

    /**
     * newAndEqQueryWrapper
     * 创建一个字段比对条件为相等，关系为并且的 queryWrapper
     *
     * @param bean  bean
     * @param clazz clazz
     * @return {@link QueryWrapper<T>}
     * @author hyluan
     * @date 2022/6/24 17:42
     */
    public static <T> QueryWrapper<T> newAndEqQueryWrapper(T bean, Class<T> clazz) {
        Field[] allDeclaredField = ReflectionUtils.getAllDeclaredField(bean);
        List<SearchObject> searchObjects = Arrays.stream(allDeclaredField).map(f -> toEqSearchObject(bean, f)).collect(Collectors.toList());
        return newConditionWrapper(searchObjects, clazz, Wrappers.query(), null);
    }

    /**
     * newAndLikeQueryWrapper
     * 创建一个字段比对条件为模糊匹配，关系为并且的 queryWrapper
     *
     * @param bean  bean
     * @param clazz clazz
     * @return {@link QueryWrapper<T>}
     * @author hyluan
     * @date 2022/6/24 17:44
     */
    public static <T> QueryWrapper<T> newAndLikeQueryWrapper(T bean, Class<T> clazz) {
        Field[] allDeclaredField = ReflectionUtils.getAllDeclaredField(bean);
        List<SearchObject> searchObjects = Arrays.stream(allDeclaredField).map(f -> toLikeSearchObject(bean, f)).collect(Collectors.toList());
        return newConditionWrapper(searchObjects, clazz, Wrappers.query(), null);
    }

    private static <T> SearchObject toEqSearchObject(T bean, Field f) {
        return toSearchObject(bean, f, isCanLikeColumn(f) ? ComparatorEnum.LIKE : ComparatorEnum.EQ);
    }

    private static <T> SearchObject toLikeSearchObject(T bean, Field f) {
        return toSearchObject(bean, f, isCanLikeColumn(f) ? ComparatorEnum.LIKE : isCanLikeColumn(ReflectionUtils.getFieldValue(bean, f.getName())) ? ComparatorEnum.LIKE : ComparatorEnum.EQ);
    }

    private static <T> SearchObject toSearchObject(T bean, Field f, ComparatorEnum comparatorEnum) {
        Object fieldValue = ReflectionUtils.getFieldValue(bean, f.getName());
        return new SearchObject().setField(f.getName()).setComparator(comparatorEnum).setCondition(ConditionEnum.AND).setValue(fieldValue == null ? null : fieldValue.toString());
    }

    public static <T> QueryWrapper<T> newConditionWrapper(List<SearchObject> body, Class<T> clazz) {
        return newConditionWrapper(body, clazz, Wrappers.query(), null);
    }

    public static <T> QueryWrapper<T> newConditionWrapper(List<SearchObject> body, Class<T> clazz, List<String> columns) {
        return newConditionWrapper(body, clazz, Wrappers.query(), columns);
    }

    /**
     * newConditionWrapper
     * 通过条件构造器创建 queryWrapper 例如：</br>
     * {
     * "rows": 10,
     * "page": 1,
     * "sc": [
     * {
     * "condition": "and",
     * "comparator": "like right",
     * "field": "name",
     * "value": "hello world"
     * },
     * {
     * "condition": "and",
     * "comparator": "eq",
     * "field": "isDel",
     * "value": 0
     * },
     * {
     * "condition": "by",
     * "comparator": "order by",
     * "field": "id,name",
     * "value": "asc"
     * }
     * ]
     * }
     * } 表示以右侧模糊匹配方式 查询name字段为hello world 开头,并且idDel为 0 的前十条数据，以 id,name 顺序排序
     *
     * @param body         body
     * @param clazz        clazz
     * @param queryWrapper 查询wrapper
     * @param columns      查询列
     * @return {@link QueryWrapper<T>}
     * @author hyluan
     * @date 2022/6/24 17:44
     */
    public static <T> QueryWrapper<T> newConditionWrapper(List<SearchObject> body, Class<T> clazz, QueryWrapper<T> queryWrapper, List<String> columns) {
        queryWrapper.setEntityClass(clazz);

        // 根据传入的模型类，获取属性名与数据库列名自建的映射关系
        Map<String, String> field2ColumnMap = getDbFieldMap(clazz);
        if (CollectionUtils.isEmpty(field2ColumnMap)) {
            throw new RuntimeException("传入的类" + clazz.getName() + "不是实体对象列");
        }

        //设置需要显示的列
        if (!CollectionUtils.isEmpty(columns)) {
            String[] columnsArray = columns.toArray(new String[0]);
            List<String> columnNamesBySearchField = getColumnNamesBySearchField(field2ColumnMap, columnsArray);
            if (columnNamesBySearchField.isEmpty()) {
                for (String filedName : columnsArray) {
                    log.info("未匹配到的select字段：{}", filedName);
                    throw new RuntimeException("未匹配到的select字段：" + filedName);
                }
            }
            queryWrapper.select(columnNamesBySearchField.toArray(new String[0]));
        }

        if (CollectionUtils.isEmpty(body)) {
            log.info("SearchObject为空:clazz:{},查询全部", clazz.getName());
            return queryWrapper;
        }

        for (SearchObject searchObject : body) {
            //有子条件
            boolean hasChildren = !CollectionUtils.isEmpty(searchObject.getChildren());
            if (hasChildren) {
                boolean isNUll = searchObject.getChildren().stream().allMatch(t -> StrUtil.isEmpty(t.getValue()) && CollectionUtils.isEmpty(t.getChildren()));
                if (isNUll) {
                    continue;
                }
                if (searchObject.getCondition().equals(ConditionEnum.AND)) {
                    queryWrapper.and(w -> newConditionWrapper(searchObject.getChildren(), clazz, w, null));
                }
                if (searchObject.getCondition().equals(ConditionEnum.OR)) {
                    queryWrapper.or(w -> newConditionWrapper(searchObject.getChildren(), clazz, w, null));
                }
                continue;
            }
            //设置数据库字段
            String[] filedNames = searchObject.getField().split(",");
            String[] columnNamesBySearchFields = getColumnNamesBySearchField(field2ColumnMap, filedNames).toArray(new String[0]);
            if (columnNamesBySearchFields.length == 0) {
                for (String filedName : filedNames) {
                    log.warn("未匹配到的查询字段：{}", filedName);
                    throw new RuntimeException("未匹配到的查询字段：" + filedName);
                }
                continue;
            }
            SearchComparator searchComparator = SEARCH_COMPARATOR_MAP.get(searchObject.getComparator());
            Assert.notNull(searchComparator, "未匹配到searchComparator:" + searchObject.getComparator());
            boolean validSuccess = Arrays.stream(filedNames).anyMatch(t -> searchComparator.valid(new DbField(t, searchObject.getValue())));
            if (validSuccess) {
                switch (searchObject.getCondition()) {
                    case AND:
                        if (columnNamesBySearchFields.length == 1) {
                            accept(columnNamesBySearchFields, searchComparator, queryWrapper, searchObject.getValue());
                        } else {
                            queryWrapper.and(w -> accept(columnNamesBySearchFields, searchComparator, w, searchObject.getValue()));
                        }
                        break;
                    case OR:
                        if (columnNamesBySearchFields.length == 1) {
                            queryWrapper.or();
                            accept(columnNamesBySearchFields, searchComparator, queryWrapper, searchObject.getValue());
                        } else {
                            queryWrapper.or(w -> accept(columnNamesBySearchFields, searchComparator, w, searchObject.getValue()));
                        }
                        break;
                    case BY:
                        accept(columnNamesBySearchFields, searchComparator, queryWrapper, searchObject.getValue());
                        break;
                    default:
                        break;
                }

            } else {
                if (StrUtil.isEmpty(searchObject.getValue())) {
                    log.trace("searchComparator:%s,值为空! field:%s,value:%s".formatted(searchComparator.comparatorEnum().name(), searchObject.getField(), searchObject.getValue()));
                } else {
                    log.debug("searchComparator:%s,校验不通过! field:%s,value:%s".formatted(searchComparator.comparatorEnum().name(), searchObject.getField(), searchObject.getValue()));
                }
            }
        }
        return queryWrapper;
    }

    private static <T> void accept(String[] filedNames, SearchComparator searchComparator, QueryWrapper<T> w1, String value) {
        for (int i = 0; i < filedNames.length; i++) {
            String filedName = filedNames[i];
            DbField dbField = new DbField(filedName, value);
            try {
                searchComparator.accept(w1, dbField);
                if (i != filedNames.length - 1) {
                    w1.or();
                }
            } catch (Exception e) {
                log.error("", e);
            }
        }
    }

    /**
     * 将搜索条件中的列转化为数据库列名
     *
     * @param field2ColumnMap
     * @param fields
     * @return
     */
    private static List<String> getColumnNamesBySearchField(Map<String, String> field2ColumnMap, String[] fields) {
        List<String> result = new ArrayList<>();
        if (fields == null || fields.length == 0) {
            return result;
        }
        for (String field : fields) {
            Assert.notNull(field, "field不能为空");
            String[] split = field.split("\\.");
            String name = split[0];
            String columnName = field2ColumnMap.get(name);
            if (StringUtils.isBlank(columnName)) {
                log.error(name + "对应的属性不存在数据库列，忽略查询");
                continue;
            }
            if (split.length > 1) {
                result.add(columnName + "." + split[1]);
            } else {
                result.add(columnName);
            }
        }
        return result;
    }

    /**
     * 将数据库模型对象中的属性名称与数据库的列名的映射关系返回，key为模型属性，value为数据库列名
     *
     * @param clz
     * @return
     */
    private static Map<String, String> getDbFieldMap(Class clz) {
        Map<String, String> result = new HashMap<>();
        Field[] fields = clz.getDeclaredFields();

        if (fields.length == 0) {
            return result;
        }

        for (Field field : fields) {
            TableId tableId = field.getAnnotation(TableId.class);
            if (tableId != null) {
                result.put(field.getName(), tableId.value());
            }
            TableField tableField = field.getAnnotation(TableField.class);
            if (tableField != null) {
                result.put(field.getName(), tableField.value());
            }
            if (tableId == null && tableField == null) {
                result.put(field.getName(), field.getName());
            }
        }
        return result;
    }

    public static <T> void setWrapperWhere(T bean, UpdateWrapper<T> updateWrapper) {
        Class<?> aClass = bean.getClass();
        Field tableIdField = ReflectionUtils.getTableIdField(aClass);
        tableIdField.setAccessible(true);
        Object value = org.springframework.util.ReflectionUtils.getField(tableIdField, bean);
        Assert.notNull(value, "主键字段值为空");
        updateWrapper.eq(tableIdField.getAnnotation(TableId.class).value(), value);
    }

    public static <T> void setWrapperSet(T bean, UpdateWrapper<T> updateWrapper) {
        Class<?> aClass = bean.getClass();
        org.springframework.util.ReflectionUtils.doWithFields(aClass, field -> {
            field.setAccessible(true);
            Object value = org.springframework.util.ReflectionUtils.getField(field, bean);
            if (value != null && !Modifier.isStatic(field.getModifiers())) {
                TableField tableField = field.getAnnotation(TableField.class);
                if (tableField != null && tableField.exist() && StrUtil.isNotEmpty(tableField.value())) {
                    value = transValue(value, tableField.typeHandler());
                    updateWrapper.set(tableField.value(), value);
                }
            }
        });
    }

    @SneakyThrows
    private static Object transValue(Object value, Class<? extends TypeHandler> clazz) {
        if (clazz != null && (clazz.isAssignableFrom(Java8TimeJacksonTypeHandler.class) || clazz.isAssignableFrom(JacksonTypeHandler.class))) {
            value = SpringUtil.getBean(ObjectMapper.class).writeValueAsString(value);
        }
        return value;
    }
}
